/*
 * Created on Apr 1, 2008
 * Created by Paul Gardner
 * 
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License only.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package org.gudy.azureus2.platform.macosx;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import org.gudy.azureus2.core3.util.FileUtil;

public class PListEditor {
    private String plistFile;

    private boolean found_bom;

    public PListEditor(String plistFile)

    throws IOException {
        this.plistFile = plistFile;

        File file = new File(plistFile);

        if (!file.exists()) {

            throw (new IOException("plist file '" + file + "' doesn't exist"));
        }

        if (!file.canWrite()) {

            throw (new IOException("plist file '" + file + "' is read only"));
        }
    }

    public void setFileTypeExtensions(String[] extensions)

    throws IOException {
        StringBuffer value = new StringBuffer();
        StringBuffer find = new StringBuffer();
        find.append("(?s).*?<key>CFBundleDocumentTypes</key>\\s*<array>.*?<key>CFBundleTypeExtensions</key>\\s*<array>");
        for (int i = 0; i < extensions.length; i++) {
            value.append("\n\t\t\t\t<string>");
            value.append(extensions[i]);
            value.append("</string>");

            find.append(".*?");
            find.append(extensions[i]);
        }
        value.append("\n\t\t\t");

        find.append(".*?</array>.*");
        String match = "(?s)(<key>CFBundleDocumentTypes</key>\\s*<array>.*?<key>CFBundleTypeExtensions</key>\\s*<array>)(.*?)(</array>)";

        setValue(find.toString(), match, value.toString());
    }

    public void setSimpleStringValue(String key, String value)

    throws IOException {
        String find = "(?s).*?<key>" + key + "</key>\\s*" + "<string>" + value + "</string>.*";
        String match = "(?s)(<key>" + key + "</key>\\s*" + "<string>)(.*?)(</string>)";
        setValue(find, match, value);
    }

    public void setArrayValues(String key, String valueType, String[] values)

    throws IOException {
        StringBuffer value = new StringBuffer();
        StringBuffer find = new StringBuffer();
        find.append("(?s).*?<key>" + key + "</key>\\s*" + "<array>");
        for (int i = 0; i < values.length; i++) {
            find.append("\\s*<" + valueType + ">" + values[i] + "</" + valueType + ">");
            value.append("\n\t\t\t\t<" + valueType + ">");
            value.append(values[i]);
            value.append("</" + valueType + ">");
        }
        find.append("\\s*</array>.*");
        value.append("\n\t\t\t");

        String match = "(?s)(<key>" + key + "</key>\\s*<array>)(.*?)(</array>)";

        setValue(find.toString(), match, value.toString());
    }

    private boolean isValuePresent(String fileContent, String match)

    throws IOException {
        // System.out.println("Searching for:\n" + match);
        return fileContent.matches(match);
    }

    /**
     * 
     * @param find
     *            the regex expression to find if the value is already present
     * @param match
     *            the regex expression that will match for the replace, it needs to capture 3 groups, the 2nd one being replaced by value
     * @param value
     *            the value that replaces the 2nd match group
     */
    private void setValue(String find, String match, String value)

    throws IOException {
        String fileContent = getFileContent();

        if (!isValuePresent(fileContent, find)) {
            // System.out.println("Changing " +plistFile);
            fileContent = fileContent.replaceFirst(match, "$1" + value + "$3");
            setFileContent(fileContent);
            touchFile();
        }
    }

    private String getFileContent() throws IOException {
        InputStreamReader reader = null;

        try {
            byte[] file_bytes = FileUtil.readFileAsByteArray(new File(plistFile));

            // handle UTF-8 encoded BOM EFBBBF

            if (file_bytes.length > 3 && file_bytes[0] == (byte) 0xEF && file_bytes[1] == (byte) 0xBB && file_bytes[2] == (byte) 0xBF) {

                found_bom = true;

                reader = new InputStreamReader(new ByteArrayInputStream(file_bytes, 3, file_bytes.length - 3));

            } else {

                found_bom = false;

                reader = new InputStreamReader(new ByteArrayInputStream(file_bytes));

            }

            // max 32KB

            int length = 32 * 1024;

            char[] buffer = new char[length];

            int offset = 0;

            int len = 0;

            while ((len = reader.read(buffer, offset, length - offset)) > 0) {
                offset += len;
            }

            String result = new String(buffer, 0, offset);

            return result;

        } finally {
            if (reader != null) {
                reader.close();
            }
        }

        // return FileUtil.readFileAsString(new File(plistFile), 64*1024, "UTF-8" );
    }

    private void setFileContent(String fileContent)

    throws IOException {
        File file = new File(plistFile);

        File backup_file = new File(plistFile + ".bak");

        if (file.exists()) {

            if (!FileUtil.copyFile(file, backup_file)) {

                throw (new IOException("Failed to backup plist file prior to modification"));
            }
        }

        boolean ok = false;

        try {

            ByteArrayOutputStream baos = new ByteArrayOutputStream(fileContent.length() + 256);

            if (found_bom) {

                // preserve UTF-8 BOM if it was found

                baos.write(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
            }

            OutputStreamWriter osw = new OutputStreamWriter(baos);

            osw.write(fileContent);

            osw.close();

            FileOutputStream out = null;

            try {

                out = new FileOutputStream(plistFile);

                out.write(baos.toByteArray());

            } finally {

                if (out != null) {

                    out.close();

                    ok = true;
                }
            }
        } finally {
            if (ok) {

                backup_file.delete();

            } else {

                if (backup_file.exists()) {

                    File bork_file = new File(plistFile + ".bad");

                    file.renameTo(bork_file);

                    file.delete();

                    backup_file.renameTo(file);
                }
            }
        }
    }

    public void touchFile() {
        File file = new File(plistFile);
        for (int i = 0; i <= 2; i++) {
            if (file != null) {
                String command[] = new String[] { "touch", file.getAbsolutePath() };

                try {
                    Runtime.getRuntime().exec(command);

                } catch (Exception e) {

                    e.printStackTrace();
                }

                file = file.getParentFile();
            }
        }
    }

    public static void main(String args[]) {
        try {
            PListEditor editor = new PListEditor("/Applications/Vuze.app/Contents/Info.plist");
            editor.setFileTypeExtensions(new String[] { "torrent", "tor", "vuze", "vuz" });
            editor.setSimpleStringValue("CFBundleName", "Vuze");
            editor.setSimpleStringValue("CFBundleTypeName", "Vuze Download");
            editor.setSimpleStringValue("CFBundleGetInfoString", "Vuze");
            editor.setArrayValues("CFBundleURLSchemes", "string", new String[] { "magnet", "dht" });
        } catch (Throwable e) {

            e.printStackTrace();
        }
    }

}
